package generator.workflow.sfj.gridgain.j2xml_impl;

import generator.datastructure.MethodDeclaration;
import generator.datastructure.WritebleClass;
import generator.datastructure.j2xml_impl.DocumentWrapper;
import generator.util.J2XMLManipulator;
import generator.util.DocTransformer;
import generator.workflow.sfj.gridgain.GridJob;
import generator.workflow.sfj.gridgain.GridResult;

import java.io.File;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/** 
 * La clase Wrapper es generada partiendo del template GridExecutorJob.xml. El mismo se trabaja
 * como un documento DOM y su estructura responde al formato impuesto por la biblioteca JavaToXml.
 * Por lo tanto, las operaciones que implementa se basan también en dicho formato.
 * @author Hirsch-Fernández
 */
public class GridJob_J2XML extends GridJob{
	
	/**Representa la ruta del archivo template que se utilizará para construir el documento que representa
	 * la clase GridJob*/
	private final String pathExecutoJobTemplate = new String ("config"+System.getProperty("file.separator")+"JavaToXml"+System.getProperty("file.separator")+"GridGainGridifier"+System.getProperty("file.separator")+"GridExecutorJob.xml");
	
	/**Objeto que se utilizará para realizar operaciones genéricas sobre clases java
	 * representadas como documentos que responden al formato impuesto por la biblioteca
	 * JavaToXml*/
	private J2XMLManipulator cm;
	
	/**Objeto utilizado para evaluar expresiones xpath sobre archivos xml*/
	private XPath xpath;
	
	/**Documento del que estrae ciertas estructuras pre armadas que utilizará durante la generación
	 * de la clase peer*/
	private Document preBuildStructures;
	
	/**Documento que representa la clase Wrapper en todo momento del proceso de construcción
	 * de la misma.*/
	private Document ejDoc = null;
	
	/**Constructor de la clase en la que se inicializan las variables que la misma utiliza para
	 * la generación de la clase Job.
	 */
	public GridJob_J2XML(){
		cm = new J2XMLManipulator();
		xpath = XPathFactory.newInstance().newXPath();
		preBuildStructures = DocTransformer.xmlToDom(new File(cm.getPreBuildStructuresPath()));
	}
	
	/**{@inheritDoc}*/
	@Override
	public void registerNewParam(MethodDeclaration md, String paramType,String paramName) {
		try {
			Element instanceVar = this.generateInstanceVar(ejDoc,paramType,paramName);
			Element paramNode = this.addCreatorNewParam(ejDoc,instanceVar);
			this.addCreatorAssigment(ejDoc,paramNode);
			this.addCallParams(ejDoc,paramNode);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}

	/**{@inheritDoc}*/
	@Override
	public void setCalledMethod(MethodDeclaration md) {
		try {
			Element method = (Element)xpath.evaluate("//"+cm.METHODCALL, ejDoc,XPathConstants.NODE);
			String methodName = md.getMethodName();
			method.setAttribute("message", methodName);
		} catch (Exception e) {
			e.printStackTrace();
		}		
	}

	/**{@inheritDoc}*/
	@Override
	public void setCastType(MethodDeclaration md) {
		Element castType;
		try {
			castType = (Element)xpath.evaluate("//"+cm.CASTEXPR+"/"+cm.TYPE,ejDoc,XPathConstants.NODE);
			castType.setAttribute("name", md.getReturnType());
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
	
	/**{@inheritDoc}*/
	@Override
	public void setClassImports(WritebleClass gridifiedClass) {
		cm.cloneImports(((DocumentWrapper)gridifiedClass).getDocument(), ejDoc);	
	}

	/**{@inheritDoc}*/
	@Override
	public void setClassPackage(String pack) {
		cm.setPackage(pack,ejDoc);		
	}

	/**{@inheritDoc}*/
	@Override
	public void setGJName(MethodDeclaration md) {
        try {
			cm.setClassName(GridJob_J2XML.getGJName(md), ejDoc);
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}		
	}

	/**{@inheritDoc}*/
	@Override
	public void setGRRefences(MethodDeclaration md) {
		try {
			Element methodCall = (Element) xpath.evaluate("/"+cm.CLASSFILENAME+"/"+cm.CLASS+"/"+cm.METHOD+
														"[@name=\"call\"]/"+cm.TYPE,ejDoc,XPathConstants.NODE);
			methodCall.setAttribute("name", GridResult.getGRName(md));
			Element newGR = (Element) xpath.evaluate("/"+cm.CLASSFILENAME+"/"+cm.CLASS+"/"+cm.METHOD+"[@name=\"call\"]/"+
													cm.BODYMETHOD+"/"+cm.RETURN+"/"+cm.NEW+"/"+cm.TYPE,ejDoc,XPathConstants.NODE);
			newGR.setAttribute("name", GridResult.getGRName(md));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**{@inheritDoc}*/
	@Override
	public void setJobType(MethodDeclaration md) {
		try {
			String typeValue = md.getClassOwner()+"Peer";
			cm.setType(typeValue,ejDoc,"/"+cm.CLASSFILENAME+"/"+cm.CLASS+"/"+cm.ATTRIBUTE+"[@name=\"job\"]/"+cm.TYPE+"[1]");
			//Parametro job del constructor
			Node firstParam = this.createParam(ejDoc,"job",typeValue);
			Element paramsExJob = (Element)xpath.evaluate("//"+cm.CONSTRUCTOR+"/"+cm.ALLPARAMS,ejDoc,XPathConstants.NODE);
			paramsExJob.appendChild(firstParam);
			//Parametro recLevel del constructor
			firstParam = this.createParam(ejDoc, "recLevel", "int");
			paramsExJob.appendChild(firstParam);
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
	
	/**{@inheritDoc}*/
	@Override
	public WritebleClass getGJ() {
		return new DocumentWrapper(ejDoc);
	}

	/**{@inheritDoc}*/
	@Override
	public void startNewGridJobClass() {
		ejDoc = DocTransformer.xmlToDom(new File(this.pathExecutoJobTemplate));		
	}
	
	/** Agrega un parámetro para la llamada recursiva del método call en base al valor de {@link param}
	 * 
	 * @param ejDoc Documento que representa el GJ.
	 * @param param Parametro del que se obtienen los datos para el nuevo parametro.
	 * @throws XPathExpressionException
	 */
	private void addCallParams(Document ejDoc, Element param) throws XPathExpressionException {
		Element argumentos = (Element)xpath.evaluate("/"+cm.CLASSFILENAME+"/"+cm.CLASS+"/"+cm.METHOD+"[@name=\"call\"]/"+cm.BODYMETHOD+"/"+cm.RETURN
				+"/"+cm.NEW+"/"+cm.ARGUMENTS+"/"+cm.CASTEXPR+"/"+cm.METHODCALL+"/"+cm.ARGUMENTS,ejDoc,XPathConstants.NODE);
		Element boolArg = (Element)argumentos.getLastChild();
		if(boolArg == null)
			argumentos.appendChild(cm.createNewTag(ejDoc,cm.VARREF,param.getAttribute("name")));
		else
			argumentos.insertBefore(cm.createNewTag(ejDoc,cm.VARREF,param.getAttribute("name")), boolArg);
	}

	
	/** Crea la asignación entre uno de los parametros del constructor del GJ y una variable de instancia
	 * 
	 * @param ejDoc Documento que representa el GJ.
	 * @param param Parametro del que se obtienen los datos para la nueva asignación.
	 * @throws XPathExpressionException
	 */
	private void addCreatorAssigment(Document ejDoc, Element param) throws XPathExpressionException {
		Element creatorBody = (Element)xpath.evaluate("/"+cm.CLASSFILENAME+"/"+cm.CLASS+"/"+cm.CONSTRUCTOR+"/"+cm.BODYMETHOD
				,ejDoc,XPathConstants.NODE);
		Element nodeToAdd = this.setAssingmentValues(ejDoc,param.getAttribute("name"),param.getAttribute("name"));
		creatorBody.appendChild(nodeToAdd);		
	}
	
	/**
	 * El método genera un Element que representa una asignación con el valor a izquiera {@link leftValue} que pertenece
	 * a una variable de instancia (accede mediante this) y a derecha {@link rightValue}, y la asocia {@link doc}.
	 * Ejemplo:
	 * 			this.leftValue = rightValue;
	 * 
	 * @param doc Document al que se asocia el nuevo Element que representa una asignación.
	 * @param leftValue Valor del lado izquierdo de la asignación.
	 * @param rightValue Valor del lado derecho de la asignación.
	 * @throws XPathExpressionException
	 */
	protected Element setAssingmentValues(Document doc,String leftValue, String rightValue) throws XPathExpressionException {
		Element assignment = (Element) xpath.evaluate("structures/structure[@id=\"constructorAssignment\"]/"+cm.ASIGMENT, this.preBuildStructures,XPathConstants.NODE);
		Element lvalue = (Element)xpath.evaluate(cm.ASSIGLEFTVALUE+"/"+cm.FIELDACCESS, assignment, XPathConstants.NODE);
		Element rvalue = (Element)xpath.evaluate(cm.VARREF, assignment, XPathConstants.NODE);
		lvalue.setAttribute("field", leftValue);
		rvalue.setAttribute("name", rightValue);
		return (Element)doc.importNode(assignment, true);
	}
	
	/** Crea un nuevo parametro en el constructor del GJ representado por {@link ejDoc},
	 *  basandose en {@link instanceVar}.<br>
	 * 	Ejemplo de {@link instanceVar} esperado: <br>
	 * 	&lt field name="varName" visibility="private"&gt <br>
     *  &lt type name="varType" /&gt <br>
     *  &lt/field&gt
	 * 
	 * @param ejDoc Documento que representa el GJ.
	 * @param instanceVar Parametro del que se obtienen los datos para el nuevo parametro.
	 * @throws XPathExpressionException
	 */
	private Element addCreatorNewParam(Document ejDoc, Element instanceVar) throws XPathExpressionException {
		Element paramsExJob = (Element)xpath.evaluate("/"+cm.CLASSFILENAME+"/"+cm.CLASS+"/"+cm.CONSTRUCTOR+"/"+cm.ALLPARAMS,ejDoc,XPathConstants.NODE);
		Element newParam = cm.createNewTag(ejDoc,cm.PARAM,instanceVar.getAttribute("name"));
		Element type = cm.createNewTag(ejDoc,cm.TYPE,((Element)instanceVar.getChildNodes().item(0)).getAttribute("name"));
		newParam.appendChild(type);
		paramsExJob.appendChild(newParam);
		return newParam;
	}
	
	/** Método encargado de generar una nueva variables de instancia del tipo de {@link paramType}
	 * 	y la asocia a {@link ejDoc} y la inserta a continuación de la declaración de la clase.
	 * 
	 * @param ejDoc Documento que representa el GJ.
	 * @param paramType String que representa el tipo de la nueva variable.
	 * @param ParamName String que representa el nombre de la variable
	 * @throws XPathExpressionException
	 */
	private Element generateInstanceVar(Document ejDoc, String paramType,String paramName) throws XPathExpressionException {
		Element varEj = this.createInstanceVar(ejDoc,paramName, paramType);
		Element clase = (Element)xpath.evaluate("/"+cm.CLASSFILENAME+"/"+cm.CLASS+"/"+cm.ATTRIBUTE,ejDoc,XPathConstants.NODE);
		clase.getParentNode().insertBefore(varEj, clase);
		return varEj;
	}
	
	/**
	 * Crea un Element que representa una variable de instancia del tipo {@link typ} 
	 * y con el nombre {@link name}. Asocia el nuevo Element a {@link doc}
	 * 
	 * @param doc Document al que se asocia el nuevo Element
	 * @param name Nombre de la variable
	 * @param type Tipo de la variable
	 * @return El Element creado
	 */	
	private Element createInstanceVar(Document doc,String name, String typ){
		Element field = cm.createNewTag(doc,cm.ATTRIBUTE,name);
		Element type = cm.createNewTag(doc,cm.TYPE,typ);
		field.setAttribute("visibility", "private");
		field.appendChild(type);
		return field;
	}
	
	/**
	 * Crea un parametro de método con el nombre {@link name} y el tipo {@link typ} 
	 * pasados como parametros asociandolo al documento {@link doc}.
	 * 
	 * @param doc Documento al que se asociara el nuevo nodo
	 * @param name nombre del nuevo parametro
	 * @param typ tipo del nuevo parametro
	 * @return El nuevo nodo parametro
	 * @throws XPathExpressionException 
	 */
	private Node createParam (Document doc,String name,String typ) throws XPathExpressionException{
		Element param = cm.createNewTag(doc, cm.PARAM, name);
		Element type = cm.createNewTag(doc, cm.TYPE, typ);
		param.appendChild(type);		
		return param;		
	}
}
